/**
 * Copyright © 2014 Creativor Studio.
 * About license information, please see LICENSE.txt.
 */
package org.rayson.server.service;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.rayson.api.Protocol;
import org.rayson.api.annotation.NotNull;
import org.rayson.api.annotation.Service;
import org.rayson.api.annotation.ServiceDefinition;
import org.rayson.api.exception.ProtocolException;
import org.rayson.api.server.FilterChain;
import org.rayson.api.server.ServerRequest;
import org.rayson.api.server.ServerResponse;
import org.rayson.api.server.ServiceFilter;
import org.rayson.api.server.exception.DuplicateServiceException;
import org.rayson.api.server.exception.NoSuchServiceException;
import org.rayson.api.server.exception.ServiceException;
import org.rayson.server.Container;
import org.rayson.server.Manager;
import org.rayson.server.rpc.DefaultFilterChain;
import org.rayson.server.rpc.ServerRequestImpl;
import org.rayson.server.rpc.ServerResponseImpl;
import org.rayson.share.RaysonConstants;
import org.rayson.share.logger.RasonLoggerFactory;
import org.slf4j.Logger;

/**
 * TODO Add type description here.
 *
 * @author creativor
 */
public class ServiceManager implements Manager {

	private static final ServiceDefinition DEFAULT_SERVICE_DEFINITION = new ServiceDefinition() {

		@Override
		public Class<? extends ServiceFilter>[] afterFilters() {
			return new Class[0];
		}

		@Override
		public Class<? extends Annotation> annotationType() {
			return ServiceDefinition.class;
		}

		@Override
		public Class<? extends ServiceFilter>[] beforeFilters() {
			return new Class[0];
		}

		@Override
		public String domain() {
			return RaysonConstants.DEFAULT_SERVICE_DOMAIN;
		}
	};

	private static final Logger LOGGER = RasonLoggerFactory
			.getLogger(ServiceManager.class);

	private static Class<? extends Protocol>[] getServiceProtocols(
			final Class<? extends Protocol> serviceClass) {

		final ArrayList resultArray = new ArrayList();
		getServiceProtocols1(resultArray, serviceClass);

		return (Class[]) resultArray.toArray(new Class[0]);
	}

	/**
	 * @param resultArray
	 */
	private static void getServiceProtocols1(final ArrayList resultArray,
			final Class<? extends Protocol> serviceClass) {
		final Class[] interfaces = serviceClass.getInterfaces();
		for (final Class<?> inf : interfaces) {
			if ((Protocol.class != inf)
					&& (Protocol.class.isAssignableFrom(inf))) {
				if (inf.getAnnotation(Service.class) != null
						&& !resultArray.contains(inf)) {
					resultArray.add(inf);
				}
			}
		}
		final Class superClass = serviceClass.getSuperclass();
		if (Protocol.class.isAssignableFrom(superClass)) {
			getServiceProtocols1(resultArray, superClass);
		}
	}

	private final Container container;

	private final Map<String, FilterChain> filterChains;

	public ServiceManager(final Container container) {
		super();
		this.container = container;
		filterChains = new HashMap<String, FilterChain>();
	}

	private ServiceFilter createFilter(final Protocol service,
			final Class<? extends ServiceFilter> filterCls)
			throws ServiceException {
		if (filterCls == null)
			throw new ServiceException("filter class should not be null");

		final ServiceFilter filter;
		try {
			filter = filterCls.newInstance();
		} catch (InstantiationException | IllegalAccessException e) {
			throw new ServiceException(
					"Failed to create new instance of filter class "
							+ filterCls, e);
		}

		filter.init(service, null);
		return filter;
	}

	/**
	 * @param service
	 * @param protocolInterface
	 * @param afterFilters
	 * @param beforeFilters
	 * @return
	 * @throws ServiceException
	 */
	private FilterChain createFilterChain(final Protocol service,
			final Class<? extends Protocol> protocolInterface,
			final ServiceFilter[] beforeFilters,
			final ServiceFilter[] afterFilters) throws ServiceException {
		final DefaultServiceFilter middleFilter = new DefaultServiceFilter(
				protocolInterface);
		middleFilter.init(service, null);
		FilterChain currentChain = null;
		FilterChain nextChain = null;
		if (afterFilters != null) {
			for (int i = afterFilters.length - 1; i >= 0; i--) {
				currentChain = new DefaultFilterChain(service, afterFilters[i],
						nextChain);
				nextChain = currentChain;
			}
		}
		currentChain = new DefaultFilterChain(service, middleFilter, nextChain);
		nextChain = currentChain;
		if (beforeFilters != null) {
			for (int i = beforeFilters.length - 1; i >= 0; i--) {
				currentChain = new DefaultFilterChain(service,
						beforeFilters[i], nextChain);
				nextChain = currentChain;
			}
		}

		return currentChain;
	}

	@Override
	public Container getContainer() {
		return container;
	}

	@Override
	public String getName() {
		return "Service Manager of" + container;
	}

	public ServerResponse invoke(@NotNull final ServerRequest request)
			throws ServiceException, NoSuchServiceException, ProtocolException {
		LOGGER.debug("Invoking request " + request);
		final ServerRequestImpl requestImpl = (ServerRequestImpl) request;
		requestImpl.parseInvocation();
		final String service = requestImpl.getInvocation().getService();
		final FilterChain filterChain = filterChains.get(service);
		if (filterChain == null)
			throw new NoSuchServiceException(service);

		final ServerResponse response = new ServerResponseImpl(requestImpl);
		filterChain.invoke(request, response);
		return response;
	}

	/**
	 * Register services.
	 * 
	 * @param service
	 *            Service instance.
	 * @throws DuplicateServiceException
	 *             If contains duplicated service in this manager.
	 * @throws ServiceException
	 *             If the service instance is not an illegal one. Or failed to
	 *             register the service.
	 */
	public synchronized void register(@NotNull final Protocol service)
			throws DuplicateServiceException, ServiceException {
		if (service == null)
			throw new IllegalArgumentException("service should not be null");

		ServiceDefinition sd = service.getClass().getAnnotation(
				ServiceDefinition.class);
		if (sd == null) {
			sd = DEFAULT_SERVICE_DEFINITION;
		}

		final String domain = sd.domain();

		final Class<? extends Protocol>[] protocols = getServiceProtocols(service
				.getClass());
		if (protocols.length == 0)
			throw new ServiceException("No service Protocol found");

		FilterChain filterChain;
		String serviceUrl;

		// Check service url first.
		for (final Class<? extends Protocol> kls : protocols) {
			serviceUrl = domain + kls.getName();
			if (filterChains.containsKey(serviceUrl))
				throw new DuplicateServiceException(serviceUrl + " is exists");
		}

		// Then make filter chains.

		final Class<? extends ServiceFilter>[] afterFilterClses = sd
				.afterFilters();
		final Class<? extends ServiceFilter>[] beforeFilterClses = sd
				.beforeFilters();
		ServiceFilter[] afterFilters = null;
		ServiceFilter[] beforeFilters = null;
		ServiceFilter filter;
		if (afterFilterClses != null) {
			afterFilters = new ServiceFilter[afterFilterClses.length];
			for (int i = 0; i < afterFilterClses.length; i++) {
				filter = createFilter(service, afterFilterClses[i]);
				afterFilters[i] = filter;
			}
		}

		if (beforeFilterClses != null) {
			beforeFilters = new ServiceFilter[beforeFilterClses.length];
			for (int i = 0; i < beforeFilterClses.length; i++) {
				filter = createFilter(service, beforeFilterClses[i]);
				beforeFilters[i] = filter;
			}
		}

		for (final Class<? extends Protocol> kls : protocols) {
			serviceUrl = domain + kls.getName();
			filterChain = createFilterChain(service, kls, beforeFilters,
					afterFilters);
			filterChains.put(serviceUrl, filterChain);
		}
	}

	@Override
	public void start(final Properties properties) throws Exception {
		// Add built in services.
		final ServerInfoImpl serverInfo = new ServerInfoImpl();
		this.register(serverInfo);
	}

	@Override
	public void stop() {

	}
}